﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data.SqlClient;
using System.Data;

namespace FileRecovery
{
	public class FileRecover
	{
		public FileRecover()
		{
		}

		public void Trawl()
		{
			using (var man = new JS.Tools.TransactionMan("FileRecovery.Properties.Settings.DeviceSyncConnectionString"))
			{
				var con = man.BuildContext<SyncDataClassesDataContext>();

				// Find newest instance of each file for each user
				var files = con.VNewestFiles
					.OrderBy(f => f.whenstarted)
					.ToArray();

				if (files.Length == 0) return;

				// get distinct usernames, and resolve home folders according to Eccles rules
				Dictionary<string, string> userdetails = files
					.Select(f => f.username)
					.Distinct()
					.ToDictionary(u => u, u => ResolveHomeDir(u));

				// Linq doesn't supportset-based delete or update, so we delete the old fashioned way
				SqlCommand deletefile = man.BuildCommand("delete from dbo.SyncedFiles where username = @username and filename = @filename and fid <= @fileid");
				SqlParameter pusername = deletefile.Parameters.Add("@username", SqlDbType.VarChar, 50);
				SqlParameter pfilename = deletefile.Parameters.Add("@filename", SqlDbType.VarChar, 300);
				SqlParameter pfileid = deletefile.Parameters.Add("@fileid", SqlDbType.Int);
				deletefile.Prepare();

				foreach (var file in files)
				{
					try
					{
						string share = userdetails[file.username];
						if (share == null) continue;

						Console.WriteLine("Extracting for user " + file.username + ", :" + file.filename);

						// Query the file fragments
						var fragments = con.FileFragments
							.Where(f => f.forfile == file.fid)
							.OrderBy(f => f.fragmentid);

						// Write them to student's account - as a temporary file
						string tempname = share + "\\~working.temp";
						using (FileStream fStream = new FileStream(tempname, FileMode.Create, FileAccess.Write))
						{
							using (BinaryWriter br = new BinaryWriter(fStream))
							{
								foreach (var frag in fragments)
									br.Write(frag.data.ToArray());
							}
						}

						// Set correct edit times
						FileInfo info = new FileInfo(tempname);
						info.CreationTime = file.lastchange;
						info.LastWriteTime = file.lastchange;

						// Atomically rename the temp file to final name
						string fname = share + file.filename;
						MakeFolder(fname);
						CheckNotExists(fname);
						File.Move(tempname, fname);

						// Remove this file (and all older versions) from db
						pfileid.Value = file.fid;
						pusername.Value = file.username;
						pfilename.Value = file.filename;
						deletefile.ExecuteNonQuery();

						man.CommitAndBegin();	// commit the removal of these file records from the db
					}
					catch (Exception ex)
					{
						Console.WriteLine("Error: {0}", ex.Message);
					}

				}

				man.Complete();
			}
		}

		/// <summary>
		/// Ensure full folder structure exists eg \a\b\c\d\qq.doc
		/// </summary>
		private void MakeFolder(string fname)
		{
			int i = fname.LastIndexOf('\\');
			if (i == -1) throw new Exception("No slash in filename");

			Directory.CreateDirectory(fname.Substring(0, i));
		}

		/// <summary>
		/// Ensure filename does not exist, moved any existing file to X.old, recursively
		/// </summary>
		private static void CheckNotExists(string fname)
		{
			if (!File.Exists(fname)) return;

			string oldname = fname + ".old";
			CheckNotExists(oldname);
			File.Move(fname, oldname);
		}

		// *** Our servers
		private static readonly string[] coServers = new string[] { "\\\\EC2K302", "\\\\EC2K303", "\\\\EC2K02", "\\\\EC2K03", "\\\\EC2K305" };

		/// <summary>
		/// Given a username, returns their home share ie \\server1\js.staff$
		/// </summary>
		private static string ResolveHomeDir(string username)
		{
			if (username.Length < 2 || username == "admin") return null;	// stop any chance of c$ or admin$

			// *** this finds shares for students - you will need to adapt it
			foreach (string s in coServers)
			{
				string share = s + "\\" + username + "$";
				if (Directory.Exists(share))
					return share + "\\MobileDevice";
			}

			// cannot find their share
			return null;
		}
	}
}
